Skip to content

Fix enforce rekey read and time limits - #766

Open
inureyes wants to merge 3 commits into
Eugeny:mainfrom
inureyes:fix/enforce-rekey-read-and-time-limits
Open

Fix enforce rekey read and time limits#766
inureyes wants to merge 3 commits into
Eugeny:mainfrom
inureyes:fix/enforce-rekey-read-and-time-limits

Conversation

@inureyes

@inureyes inureyes commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Description

Limits exposes rekey_read_limit and rekey_time_limit, but the only place that consults them is Encrypted::flush, which is reached on the write path. A session that mostly downloads, or one that idles, keeps the same keys no matter how the limits are configured. rekey_write_limit is also off by the current epoch's cleartext KEX packets, because SSHBuffer::bytes is never reset when a cipher is installed.

  • cipher::read counts inbound payload bytes, and the client and server loops compare that count against rekey_read_limit, resetting it when a key exchange completes.
  • PacketWriter::set_cipher resets the write counter so each key epoch starts at zero.
  • Both loops arm a deadline from rekey_time_limit and Encrypted::last_rekey, so an idle session still rekeys on time.
  • Automatic rekeying is gated on completed authentication. Without that, a small RekeyLimit makes pre-authentication traffic start a second key exchange while the first is still being set up. On the server, USERAUTH_SUCCESS is flushed while the state is still InitCompression, so deferred server compression is activated before KEXINIT when rekeying is the first post-authentication write.

Session::initiate_rekey and Msg::Rekey are unchanged and still rekey on demand.

Ten tests cover the read limit, the deadline, the pre- and post-authentication gate on both sides, and the epoch reset. Dropping the byte accounting makes the two sshbuffer tests fail.

compression::tests::partial_flush_packets_round_trip fails on main at d3ae702 as well, and #757 looks like the fix. Everything else passes.

...

AI Usage

Choose the level of AI involvement for this PR.

  • Fully vibe coded
  • AI-designed, AI-coded, manually checked
  • Human-designed, AI-coded
  • Human-designed, human-coded (includes AI autocompletions and boilerplate gen)

This is not to block AI contributions but rather to speed up PR review (saves time on trying to deduce the logic behind AI hallucinations).

…ation

`Limits` exposes `rekey_read_limit` and `rekey_time_limit`, but `Encrypted::flush` is only reached on the write path, so neither limit could fire. A session that mostly downloads, or one that idles, kept the same keys indefinitely no matter how the limits were configured. `rekey_write_limit` also counted the cleartext KEX packets of the current epoch, because `SSHBuffer::bytes` was never reset when a new cipher was installed.

Count inbound payload bytes in `cipher::read`, reset the write counter in `PacketWriter::set_cipher` so each key epoch starts at zero, and arm a rekey deadline in the client and server event loops so an idle session still rekeys on time.

Gate automatic rekeying on completed authentication in both loops. Otherwise a small `RekeyLimit` makes the pre-authentication traffic start a second key exchange while the first one is still being set up. On the server, `USERAUTH_SUCCESS` is flushed while the state is still `InitCompression`, so activate deferred server compression before emitting `KEXINIT` when rekeying is the first post-authentication write.

Explicit `Session::initiate_rekey` and the `Msg::Rekey` path are unchanged and still rekey on demand.
@inureyes inureyes changed the title Fix/enforce rekey read and time limits Fix enforce rekey read and time limits Sep 4, 2026
`#[tokio::test]` is a proc macro, so unlike the built-in `#[test]` it expands even when the crate is not compiled in test mode. `sshbuffer.rs` keeps its tests at module top level rather than in a `#[cfg(test)] mod`, so the new async test broke `cargo build --target wasm32-wasip1 --no-default-features --features flate2,ring` with "The #[tokio::test] macro requires rt or rt-multi-thread".

Gate that one test on `cfg(test)` so the attribute is stripped before expansion. Verified with the CI command on 1.89.0 against `wasm32-wasip1`, and `cargo test -p russh --lib` still runs all 195 tests including this one.
@inureyes

inureyes commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Build-WASM is fixed in 4067314. The Clippy and Minimal-versions failures predate this PR and come from main on the 1.91 toolchain. #768 fixes both, if that helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant